package com.iocaop.uaa.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.iocaop.uaa.security.UserDetails.UserDetailsPasswordServiceImpl;
import com.iocaop.uaa.security.UserDetails.UserDetailsServiceImpl;
import com.iocaop.uaa.security.filter.RestAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 安全配置  打印debug信息  json登录
 */
@EnableWebSecurity(debug = true)
@RequiredArgsConstructor
@Import(SecurityProblemSupport.class)
@Order(99)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final ObjectMapper objectMapper;
    private final SecurityProblemSupport securityProblemSupport;
    private final DataSource dataSource;
    private final UserDetailsServiceImpl userDetailsService;
    private final UserDetailsPasswordServiceImpl userDetailsPasswordService;
    /**
     * 认证管理
     * @param auth
     * @throws Exception
     */
    @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .userDetailsPasswordManager(userDetailsPasswordService)
//            .jdbcAuthentication()
//            .dataSource(dataSource)
//            .usersByUsernameQuery("select username, password,enabled from lzc_users where username = ? ")
//            .authoritiesByUsernameQuery("select username,authority from lzc_roles where username = ?")
//            .withDefaultSchema()
            .passwordEncoder(passwordEncoder());
//            .withUser("admin").password("{bcrypt}$2a$10$2CJGv23GOk7WMXmGhmN95OwI8TwooAEVQY82FNUgsti.ojjpXlAXm").roles("ADMIN")
//            .and().withUser("lzc").password("{SHA-1}{oftkwH/HMIA+w7beZJ3lBxOE3CSnZCbZXrXdU+De/pM=}e885b986a1aac96a2c56358e03ec9ae864d3776a").roles("ADMIN");

    }
    @Bean
    PasswordEncoder passwordEncoder(){
        // 假设这是老的编码格式
        String idForDefault = "bcrypt";
        // 定义一个Map 存放多种编码格式
       Map<String, PasswordEncoder> encoders = new HashMap<>();
       // 加入老的编码格式
       encoders.put(idForDefault,new BCryptPasswordEncoder());
       // 加入新的编码格式
       encoders.put("SHA-1",new MessageDigestPasswordEncoder("SHA-1"));
       // 参数1是填写默认的编码格式，参数2为多种编码格式的集合
       return new DelegatingPasswordEncoder(idForDefault,encoders);
    }

    /**
     * 实例化自定义filter
     * @return
     */
    private RestAuthenticationFilter  restAuthenticationFilter() throws Exception{
        RestAuthenticationFilter filter = new RestAuthenticationFilter(objectMapper);
        // 在过滤器中加入登录成功处理器
        filter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler());
        // 在过滤器中加入登录失败处理器
        filter.setAuthenticationFailureHandler(getAuthenticationFailureHandler());
        // 设置认证管理器
        filter.setAuthenticationManager(authenticationManager());
        // 这个过滤器应有在哪个路径
        filter.setFilterProcessesUrl("/auth/login");
        return filter;
    }
    /**
     * 权限管理
     * @param http
     * @throws Exception
     */
    @Override protected void configure(HttpSecurity http) throws Exception {
        // 所有请求都需要认证
        http
            // 只需要关注匹配到的url 进行处理
            .requestMatchers(req->req.mvcMatchers("/auth/**/","/admin/**","/api/**"))
            // 配置统一异常处理
            .exceptionHandling(exp->exp
                .accessDeniedHandler(securityProblemSupport)
                .authenticationEntryPoint(securityProblemSupport)
            )
            .authorizeRequests()
            .antMatchers("/auth/login").permitAll()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/api/**").hasRole("NORMAL")
            .antMatchers("/user/**").hasRole("admin")
            .antMatchers("/auth/register").permitAll()
            .anyRequest().authenticated()
            // 指定登录时使用自定义过滤器 解析json
            .and().addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
//            // 配置认证为默认
//            .and().httpBasic(Customizer.withDefaults())
            .csrf(AbstractHttpConfigurer::disable)
            .httpBasic(Customizer.withDefaults())
            .sessionManagement().disable();
    }

    /**
     * 退出登录成功的处理器
     * @return
     */
    private LogoutSuccessHandler getLogoutSuccessHandler() {
        return (request, response, authentication) -> {
            // 设置响应编码
            response.setCharacterEncoding("UTF-8");
            // 设置返回码
            response.setStatus(HttpStatus.OK.value());
            // 设置返回内容类型
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            Map<String, String> map = new HashMap<>();
            map.put("title","退出登录成功");
            ObjectMapper objectMapper = new ObjectMapper();
            response.getWriter().println(objectMapper.writeValueAsString(map));
        };
    }

    /**
     * 登录失败处理器
     * @return
     */
    private AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return (request, response, exception) -> {
            // 响应状态码
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            // 返回content的格式
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            // 设置编码
            response.setCharacterEncoding("UTF-8");
            // 创建一个map对象
            Map<String, String> map = new HashMap<>();
            map.put("title","认证失败");
            map.put("details",exception.getMessage());
            // 创建ObjectMapper用于转换json格式
            ObjectMapper objectMapper = new ObjectMapper();
            // 写到页面
            response.getWriter().println(objectMapper.writeValueAsString(map));
        };
    }

    /**
     * 登录成功处理器
     * @return
     */
    private AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
        return (request,response,authentication)->{
        // 设置返回码
        response.setStatus(HttpStatus.OK.value());
            ObjectMapper objectMapper = new ObjectMapper();
            // ObjectMapper 可以将对象以json形式写到页面
            response.getWriter().println(objectMapper.writeValueAsString(authentication));
        };
    }

    @Override public void configure(WebSecurity web) throws Exception {
        // 配置常用静态资源、
        web.ignoring().mvcMatchers("/public/**")
            .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
        // 配置错误页、h2数据库 不经过过滤器链
        web.ignoring().antMatchers("/error","/h2-console/**");
    }
}
